package com.jason.wsclient.client;


import com.neovisionaries.ws.client.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;


/**
 * WebSocketClient 是一个Spring管理的组件，实现了ApplicationRunner接口，
 * 在应用启动时自动执行run方法以连接WebSocket服务器。
 * 支持心跳检测、自动重连和手动断开。
 */
@Component
public class WebSocketClient implements ApplicationRunner {
    // SLF4J 日志记录器，用于打印运行时日志。
    private static final Logger log = LoggerFactory.getLogger(WebSocketClient.class);

    //客户端标识，可用于日志区分不同实例。
    private String clientId = "JaosnClient";
    //WebSocket服务器地址，格式: ws://host:port/path
    private String wsUrl =  "ws://localhost:8081/ws";

    // nv-websocket-client 创建的 WebSocket 连接实例。
    private WebSocket ws;

    //单线程调度器，用于调度心跳和重连任务。
    private final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
    //当前心跳任务的Future引用，用于后续取消。
    private volatile ScheduledFuture<?> heartbeatTask;

    //上次收到PONG帧的时间戳，初始为当前系统时间
    private long lastPongTime = System.currentTimeMillis();
    //最大重连延迟(秒)，用于指数退避上限。
    private static final long MAX_RECONNECT_DELAY = 60;
    //当前重连延迟(秒)，初始为2秒，重连失败后以指数方式增长。
    private long reconnectDelay = 2;

    //定义时间格式
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * Spring Boot启动后回调，触发首次连接。
     */
    @Override
    public void run(ApplicationArguments args) throws Exception {
        connectWS();
    }

    /**
     * 连接websocket服务端并设置自动重连，如果连接失败，会触发重连逻辑。
     */
    public void connectWS() {
        try {
            log.info("clientId: {}, start connecting to wsUrl: {}", clientId, wsUrl);
            // 创建WebSocket连接
            ws = new WebSocketFactory().setConnectionTimeout(5000).createSocket(wsUrl);
            //注册回调监视器
            ws.addListener(new WebSocketAdapter() {

                /**
                 * 连接成功回调，启动心跳并发送初始消息，重置重连延迟。
                 */
                @Override
                public void onConnected(WebSocket websocket, Map<String, List<String>> headers) {
                    log.info("{} WebSocket connected", clientId);
                    startHeartbeatTask();
                    ws.sendText("Hello, Server!");
                    reconnectDelay = 2;
                }

                /**
                 * 收到PONG帧回调，更新lastPongTime。
                 */
                @Override
                public void onPongFrame(WebSocket websocket, WebSocketFrame frame) {
                    lastPongTime = System.currentTimeMillis();
                    log.debug("{} Received PONG {}", clientId , LocalDateTime.now().format(formatter));
                }

                /**
                 * 收到文本消息回调，打印消息内容。
                 */
                @Override
                public void onTextMessage(WebSocket websocket, String text) {
                    log.info("{} websocketReceive: {}", clientId, text);
                }

                /**
                 * 连接断开回调，停止心跳并安排重连。
                 */
                @Override
                public void onDisconnected(WebSocket websocket, WebSocketFrame serverCloseFrame,
                                           WebSocketFrame clientCloseFrame, boolean closedByServer) {
                    log.info("{} WebSocket disconnected", clientId);
                    stopHeartbeatTask();    //停止心跳
                    scheduleReconnect();    //安排重连
                }

                /**
                 * 出现错误回调，打印异常并尝试重连。
                 */
                @Override
                public void onError(WebSocket websocket, WebSocketException cause) {
                    log.error("{} WebSocket error", clientId, cause);
                }
            }).connect();   //发起连接

        } catch (Exception e) {
            // 连接创建失败时打印错误并安排重连
            log.error("{} connectWS connect error: {}", clientId, e.getMessage(), e);
            scheduleReconnect();
        }
    }

    /**
     * 启动心跳定时任务：每10秒发送一次PING，并检测PONG响应超时。
     */
    private void startHeartbeatTask() {
        //如果旧心跳任务未完成，则取消
        if (heartbeatTask != null && !heartbeatTask.isDone()) {
            heartbeatTask.cancel(true);
            log.info("{} 旧心跳任务取消", clientId);
        }

        log.info("{} 启动心跳定时任务", clientId);
        lastPongTime = System.currentTimeMillis();

        // 安排每10秒一次的定时任务
        heartbeatTask = scheduler.scheduleAtFixedRate(() -> {
            try {
                if (ws != null && ws.isOpen()) {
                    ws.sendPing();
                    //获取当前时间并格式化
                    String currentTime = LocalDateTime.now().format(formatter);

                    log.debug("{} Sent PING {}", clientId,currentTime);
                }
                // 检查距离上次PONG的空闲时间
                long idle = System.currentTimeMillis() - lastPongTime;
                log.debug("{} (自上次收到 PONG 响应): {}s", clientId, idle / 1000.0);
                // 超过30秒未响应则断开连接以触发重连
                if (idle > 30_000) {
                    log.warn("{} WebSocket连接超时 ({}ms) 未收到PONG，断开连接", clientId, idle);
                    if (ws != null) ws.disconnect();
                }
            } catch (Exception e) {
                log.error("{} 心跳任务异常", clientId, e);
            }
        }, 0, 10, TimeUnit.SECONDS);
    }

    /**
     * 停止心跳任务
     */
    private void stopHeartbeatTask() {
        if (heartbeatTask != null) {
            heartbeatTask.cancel(true);
            heartbeatTask = null;
            log.info("{} 心跳任务已停止", clientId);
        }
    }

    /**
     * 触发重连，延时重连策略，指数退避
     */
    private void scheduleReconnect() {
        long delay = Math.min(reconnectDelay, MAX_RECONNECT_DELAY);
        log.info("{} 将在 {} 秒后重连...", clientId, delay);
        scheduler.schedule(() -> {
            try {
                connectWS();
            } finally {
                reconnectDelay = Math.min(reconnectDelay * 2, MAX_RECONNECT_DELAY);
            }
        }, delay, TimeUnit.SECONDS);
    }

    /**
     * 主动关闭连接（可选）
     */
    public void disconnect() {
        try {
            if (ws != null) {
                ws.disconnect();
                stopHeartbeatTask();
                log.info("{} WebSocket主动关闭", clientId);
            }
        } catch (Exception e) {
            log.error("{} WebSocket关闭异常", clientId, e);
        }
    }

}

